/*
 *  Copyright 2008 The MITRE Corporation (http://www.mitre.org/). All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.mitre.lattice.lattice;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;

import org.jgap.Gene;
import org.jgap.impl.IntegerGene;
import org.mitre.mrald.util.Config;
import org.mitre.mrald.util.MraldException;
/**
 *  Sample fitness function for the MakeChange example.
 *
 *@author     jchoyt
 *@created    November 12, 2003
 */
public class LatticeFunctionsImpl
{

    public final static String KEY = LatticeFunctionsImpl.class.getName();
    public final static Logger log = Logger.getLogger( KEY );
    //log.setLevel(Level.FINEST);



    //public static int MAX_DISTANCE = 0;
    /**
     *  Description of the Field
     */
    public static int NO_OF_NODES = 0;

    /**
     *  Description of the Field
     */
    public static String ROOT_LABEL = "Public";

    /**
     *  Description of the Field
     */
    public static int screenWidth = 900;

    private static boolean isSet = false;
    /*
     *  These four hashmaps will facilitate the process at critical
     *  points. i.e.within the genetic algorithm functions.
     *  These will be called alot
     */
    /*
     *  A Hashmap to map the Integer within the Chromosome to the Node
     */
    private static HashMap<Integer,LatticeNode> mapGeneToNode = new HashMap<Integer,LatticeNode>();

    /*
     *  A Hashmap to keep count of the no. of nodes in each level
     */
    private static HashMap<Integer,Integer> mapLevelCount = new HashMap<Integer,Integer>();

    /*
     *  A Hashmap to map the Node to the Integer within the Chromosome
     */
    private static HashMap<LatticeNode,Integer> mapNodeToGene = new HashMap<LatticeNode,Integer>();

    /*
     *  A Hashmap to map the level of the Node to the node
     */
    private static HashMap<LatticeNode,Integer> maplevelOfNode = new HashMap<LatticeNode,Integer>();

    private static int maxLevel = 0;
    private static int maxNodePerLevel = 0;

    private static LatticeNode rootNode = null;
    public static int screenHeight = 700;


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     */
    public LatticeFunctionsImpl()
    {
    		//MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "LatticeFunctionsImpl : Start " );

    }

 /**
     *  Description of the Method
     *
     *@param  nowSet  The new isSet value
     */
    public boolean getIsSet()
    {
	    return isSet;
    }
    /**
     *  Description of the Method
     *
     *@param  nowSet  The new isSet value
     */
    public void setIsSet( boolean nowSet )
    {
        isSet = nowSet;
        //If false then this is a new lattice.
        if ( !isSet )
        {
            reset();
        }
    }


    /**
     *  Cycle through the number of nodes and get the maximum possible no of
     *  links if each node is linked to each node in level below. (Not exact but
     *  try for now)
     *
     *@return    The allLinks value
     */
    public int getAllLinks()
    {
        int noOfLinks = 0;
        int n1 = 0;
        int n2 = 0;

        for ( int i = 1; i < mapLevelCount.size(); i++ )
        {

            n1 = mapLevelCount.get( new Integer( i ) ).intValue();
            n2 = mapLevelCount.get( new Integer( i - 1 ) ).intValue();
            noOfLinks = noOfLinks + n1 * n2;
        }

        return noOfLinks;
    }


    /**
     *  Cycle through the number of nodes and get the maximum possible no of
     *  links if each node is linked to each node in level below. (Not exact but
     *  try for now)
     *
     *@return    The avgDistance value
     */
    public int getAvgDistance()
    {
        //The average distance is hypothenus the min x distance and y (cell height)
        double avgDist = Math.sqrt( getBoxWidth() * getBoxWidth() / 4 + getBoxHeight() * getBoxHeight() );

        return ( int ) avgDist;
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  screenHeight  Description of the Parameter
     *@return               The boxHeight value
     */
    public int getBoxHeight( int screenHeight )
    {
        return screenHeight / maxLevel;
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@return    The boxHeight value
     */
    public int getBoxHeight()
    {
        return screenHeight / maxLevel;
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  screenWidth  Description of the Parameter
     *@return              The boxWidth value
     */
    public  int getBoxWidth( int screenWidth )
    {
        if (maxNodePerLevel != 0)
            return screenWidth / maxNodePerLevel;
	else
	    return 1;
    }


    /**
     *  Gets the boxWidth attribute of the LatticeFunctions class
     *
     *@return    The boxWidth value
     */
    public int getBoxWidth()
    {
	if (maxNodePerLevel != 0)
            return screenWidth / maxNodePerLevel;
	else
	    return 1;
    }




    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  x1  Description of the Parameter
     *@param  x2  Description of the Parameter
     *@param  y1  Description of the Parameter
     *@param  y2  Description of the Parameter
     *@return     The distanceBetweenNodes value
     */
    public  double getDistanceBetweenNodes( double x1, double x2, double y1, double y2 )
    {
        double xd = x2 - x1;
        double yd = y2 - y1;
        return Math.sqrt( xd * xd + yd * yd );
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  x1  Description of the Parameter
     *@param  x2  Description of the Parameter
     *@param  yd  Description of the Parameter
     *@return     The distanceBetweenNodes value
     */
    public double getDistanceBetweenNodes( int x1, int x2, int yd )
    {
        double xd = x2 - x1;
        return Math.sqrt( xd * xd + yd * yd );
    }



    /*
     *  Description of the Method
     *
     *  @param  root  Description of the Parameter
     *  @return       Description of the Return Value
     */
    /**
     *  Gets the flatLattice attribute of the LatticeFunctions class
     *
     *@param  root  Description of the Parameter
     *@return       The flatLattice value
     */
    public ArrayList getFlatLattice( LatticeNode root )
    {
        //If this has already been initialized, then just return all the nodes.
        if ( isSet )
        {
            return getFlatLattice();
        }

        setIsSet( true );
        //make sure that this is set so that INitialization will
        //only be carried out once per lattice

        ArrayList<LatticeNode> flattenLattice = new ArrayList<LatticeNode>();
        flattenLattice = addRoot( flattenLattice, root );

        flattenLattice = flattenLattice( flattenLattice, root, 0 );
        return flattenLattice;
    }


    /**
     *  Gets the flatLattice attribute of the LatticeFunctions class
     *
     *@return    The flatLattice value
     */
    public ArrayList getFlatLattice()
    {
        //make sure that this is set so that INitialization will
        //only be carried out once per lattice
        ArrayList<LatticeNode> flatLattice = new ArrayList<LatticeNode>();
        java.util.Iterator<LatticeNode> iter = maplevelOfNode.keySet().iterator();

        while ( iter.hasNext() )
        {
            flatLattice.add( iter.next() );
        }
        return flatLattice;
    }


    /**
     *  Return the height of the screen
     *
     *@return    The height value
     */
    public  int getHeight()
    {
        return screenHeight;
    }


    /**
     *@param  node  Description of the Parameter
     *@return       The integer value
     */
    public Integer getInteger( LatticeNode node )
    {
        return mapNodeToGene.get( node );
    }


    /**
     *  Get max values
     *
     *@param  node  Description of the Parameter
     *@return       The levelOfNode value
     */
    public int getLevelOfNode( LatticeNode node )
    {
        return maplevelOfNode.get( node ).intValue();
    }


    /**
     *  Gets the mapGeneToNode attribute of the LatticeFunctions class
     *
     *@return    The mapGeneToNode value
     */
    public HashMap getMapGeneToNode()
    {
        return mapGeneToNode;
    }


    /**
     *  Gets the mapNodeToGene attribute of the LatticeFunctions class
     *
     *@return    The mapNodeToGene value
     */
    public HashMap getMapNodeToGene()
    {
        return mapNodeToGene;
    }


    /**
     *  Get max values
     *
     *@return    The maxLevel value
     */
    public int getMaxLevel()
    {
        return maxLevel;
    }


    /**
     *  Get max values
     *
     *@return    The maxNodesPerLevel value
     */
    public int getMaxNodesPerLevel()
    {
        return maxNodePerLevel;
    }


    /**
     *  Gets the nodes that are on the highest level given a starting node.
     *
     *@param  root  Description of the Parameter
     *@return       The nodesOnLevel value
     */
    public ArrayList getMostDominant( LatticeNode root )
    {
        initialize( root );
        return getNodesOnLevel( getMaxLevel() - 1 );
    }


    /**
     *  Gets the nodes that are on the highest level given a starting node.
     *
     *@param  levelDown  Description of the Parameter
     *@return            The nodesOnLevel value
     */
    public ArrayList getMostDominant( int levelDown )
    {
        return getNodesOnLevel( getMaxLevel() - 1 - levelDown );
    }


    /**
     *  Store the maximum Number of nodes in a level
     *
     *@return    The noOfGrids value
     */
    public int getNoOfGrids()
    {
        return maxLevel * maxNodePerLevel;
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  nodeInteger  Description of the Parameter
     *@return              The node value
     */
    public LatticeNode getNode( Integer nodeInteger )
    {
        return mapGeneToNode.get( nodeInteger );
    }


    /**
     *@param  level  Description of the Parameter
     *@return        The nodesOnLevel value
     */
    public ArrayList<LatticeNode> getNodesOnLevel( int level )
    {
        Integer thisLevel = new Integer( level );

        ArrayList<LatticeNode> onThisLevel = new ArrayList<LatticeNode>();
        //Get all the nodes that belong to this level
        java.util.Iterator iter = maplevelOfNode.keySet().iterator();

        while ( iter.hasNext() )
        {
            LatticeNode node = ( LatticeNode ) iter.next();
            if ( maplevelOfNode.get( node ).equals( thisLevel ) )
            {
                onThisLevel.add( node );
            }
        }

        return onThisLevel;
    }


    /**
     *@param  thisNode  Description of the Parameter
     *@return           The nodesOnSameLevel value
     */
    public ArrayList getNodesOnSameLevel( LatticeNode thisNode )
    {
        Integer thisLevel = maplevelOfNode.get( thisNode );

        ArrayList<Integer> onThisLevel = new ArrayList<Integer>();
        //Get all the nodes that belong to this level
        java.util.Iterator iter = maplevelOfNode.keySet().iterator();

        while ( iter.hasNext() )
        {
            LatticeNode node = ( LatticeNode ) iter.next();
            if ( maplevelOfNode.get( node ).equals( thisLevel ) )
            {
                onThisLevel.add( LatticeFunctions.getInteger( node ) );
            }
        }

        return onThisLevel;
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  node  Description of the Parameter
     *@return       The parents value
     */
    public ArrayList getParents( LatticeNode node )
    {
        return node.getParents();
    }


    /**
     *  Takes the x value of the Node, and 'Snaps to Grid' to neaten
     *
     *@return    The rootNode value
     */
    public LatticeNode getRootNode()
    {
        return rootNode;
    }

/**
     *  Takes the x value of the Node, and 'Snaps to Grid' to neaten
     *
     *@return    The rootNode value
     */
    public void setRootNode(LatticeNode rootNode)
    {
        LatticeFunctionsImpl.rootNode = rootNode;
    }

    /**
     *  Return the width of the screen
     *
     *@return    The width value
     */
    public int getWidth()
    {
        return screenWidth;
    }


    /**
     *  Determine the maximum distance if all the grid positions were filled and
     *  were joined to their neighbours.
     *
     *@param  nodeInteger  Description of the Parameter
     *@return              The y value
     */
    public int getY( Integer nodeInteger )
    {
        LatticeNode node = mapGeneToNode.get( nodeInteger );
        return ( maxLevel - getLevelOfNode( node ) ) * getBoxHeight();
    }


    /**
     *  Flatten the LatticeTree into an ArrayList to be used by the Generic
     *  Algorithms
     *
     *@param  flatLattice   The feature to be added to the Root attribute
     *@param  thisRootNode  The feature to be added to the Root attribute
     *@return               Description of the Return Value
     */
    public ArrayList<LatticeNode> addRoot( ArrayList<LatticeNode> flatLattice, LatticeNode thisRootNode )
    {
        rootNode = thisRootNode;
        flatLattice.add( rootNode );
        int geneIndex = flatLattice.size() - 1;
        mapGeneToNode.put( new Integer( geneIndex ), rootNode );
        mapNodeToGene.put( rootNode, new Integer( geneIndex ) );

        changeNodesPerLevel( rootNode, new Integer( 0 ) );

        return flatLattice;
    }


    /**
     *  Description of the Method
     *
     *@return    Description of the Return Value
     */
    public int calcMaxDistance()
    {
        return getAllLinks() * getAvgDistance();
    }


    /**
     *  Store the maximum Number of nodes in a level
     *
     *@return    Description of the Return Value
     */
    public int calcMaxNodesPerLevel()
    {
        int max = 0;
        int levelNo = 0;
        java.util.Iterator iter = mapLevelCount.values().iterator();
        while ( iter.hasNext() )
        {
            levelNo = ( ( Integer ) iter.next() ).intValue();
            if ( levelNo > max )
            {
                max = levelNo;
            }
        }
        maxNodePerLevel = max;
        return max;
    }


    /**
     *  SLook for the level that the node was associated with. Decrease the
     *  count on this level. INcrease in the new level
     *
     *@param  node      Description of the Parameter
     *@param  newLevel  Description of the Parameter
     */
    public int changeNodesPerLevel( LatticeNode node, Integer newLevel )
    {
        //If this level doesn't have an entry add one
        if ( !mapLevelCount.containsKey( newLevel ) )
        {
            mapLevelCount.put( newLevel, new Integer( 1 ) );
            maplevelOfNode.put( node, newLevel );
            return newLevel.intValue();
        }

        //CHeck to see if this node was previously added
        if ( maplevelOfNode.containsKey( node ) )
        {
            //Find the level that the node was previously associated with
            Integer oldLevel = maplevelOfNode.get( node );

            //If old Level is less than the new level then don't do anything
            if ( oldLevel.intValue() > newLevel.intValue() )
            {
                return oldLevel.intValue();
            }
            int oldNoOfNodes = mapLevelCount.get( oldLevel ).intValue();

            //Decrease the vaue of Nodes by one
            mapLevelCount.put( oldLevel, new Integer( --oldNoOfNodes ) );

        }

        //Update
        maplevelOfNode.put( node, newLevel );
        //Update the value in mapLevelOfnODE
        int oldNoOfNodesInNewLevel = mapLevelCount.get( newLevel ).intValue();
        mapLevelCount.put( newLevel, new Integer( ++oldNoOfNodesInNewLevel ) );

	return newLevel.intValue();
    }


    /**
     *  Store the number of levels
     *
     *@param  currentLevel  Description of the Parameter
     */
    public void checkMaxLevel( int currentLevel )
    {
        if ( currentLevel > maxLevel )
        {
            maxLevel = currentLevel;
        }
    }


    /**
     *  Holding place function.
     *  For case where the use wishes to get a subset of the
     * Lattice Tree. WHich may be a composite of child tree and
     * other node types.
     *
     *@param  keyNode                       Description of the Parameter
     *@return                               Description of the Return Value
     *@exception  NullLatticeNodeException  Description of the Exception
     */
    public Object getSubTree(LatticeTree childTree , LatticeNode keyNode)
        throws NullLatticeNodeException, InvalidLatticeStructureException
    {
        /*
         *
         */

		LatticeNode childRoot = childTree.getRootNode();

		ArrayList labNodes = new ArrayList();

		labNodes = addLabNodes(rootNode, labNodes);

		log.info("No of Lab Nodes: " + labNodes.size());

		for (int i = 0; i < labNodes.size(); i++)
		{

			LatticeNode currNode = Config.getLatticeFactory().copyNode((LatticeNode) labNodes.get(i));
			try
			{
			    childTree.searchTree( currNode ) ;
			}
			catch(NodeNotFoundException e)
			{
			    log.info("Adding currNode " + currNode.getName());
			    childRoot.addParent(currNode);
			    currNode.addChild(childRoot);
			}

		}

		 return childRoot;
    }

    /**
     *  Function to take a higher level node and make a new LatticeTree based upon
     *  the Childs and the direct parents of these Child objects. The User will
     *  have read access to all nodes. But not have write access to Nodes that are
     *  not direct children. (e.g. the parents of a child)
     *
     *@param  keyNode                       Description of the Parameter
     *@return                               Description of the Return Value
     *@exception  NullLatticeNodeException  Description of the Exception
     */
    public Object getChildTree(LatticeNode keyNode)
        throws NullLatticeNodeException, InvalidLatticeStructureException
    {
        /*
         *
         */
        LatticeTree childTree = new LatticeTree();

        if (keyNode == null)
        {
            throw new NullLatticeNodeException("LATT-005");
        }
        rootNode = null;

        return copyAllChildren(keyNode, Config.getLatticeFactory().copyNode(keyNode), childTree);
    }

 /**
     *  Function to take a higher level node and make a new LatticeTree based upon
     *  the Childs and the direct parents of these Child objects. The User will
     *  have read access to all nodes. But not have write access to Nodes that are
     *  not direct children. (e.g. the parents of a child)
     *
     *@param  keyNode                       Description of the Parameter
     *@return                               Description of the Return Value
     *@exception  NullLatticeNodeException  Description of the Exception
     */
    public Object getChildTreeNoParents(LatticeNode keyNode)
        throws NullLatticeNodeException, InvalidLatticeStructureException
    {
        /*
         *
         */
        LatticeTree childTree = new LatticeTree();

        if (keyNode == null)
        {
            throw new NullLatticeNodeException("LATT-005");
        }
        rootNode = null;

        return copyOnlyChildren(keyNode, Config.getLatticeFactory().copyNode(keyNode), childTree);
    }

    /**
     *  Flatten the LatticeTree into an ArrayList to be used by the Generic
     *  Algorithms
     *
     *@param  keyNode                               Description of the Parameter
     *@param  copyNode                              Description of the Parameter
     *@param  childTree                             Description of the Parameter
     *@return                                       Description of the Return
     *      Value
     *@exception  InvalidLatticeStructureException  Description of the Exception
     */
    public Object copyAllChildren( LatticeNode keyNode, LatticeNode copyNode, LatticeTree childTree )
        throws InvalidLatticeStructureException
    {
        try
        {

            //Return the final Node that does not have any children.
            //This should be the Public Node
            //There will be one 'public' node for each branch in the ChildTree
            if ( keyNode.countChildNodes() == 0 )
            {
                if ( childTree.getRootNode() == null )
                {
                    childTree.setRootNode( copyNode );
                    return copyNode;
                }

                LatticeNode childRootNode = childTree.getRootNode();
                //Make sure that all branches are added to the root node
                //As copyNode will become the new Root Node
                for ( int i = 0; i < copyNode.getParents().size(); i++ )
                {
                    childRootNode.addParent( copyNode.getParents().get( i ) );
                }
                childTree.setRootNode( copyNode );
                return copyNode;
            }
            Object endNode = null;

            ArrayList children = keyNode.getChildren();

            //Get the CHildren and Add to the new LatticeTree
            for ( int i = 0; i < keyNode.countChildNodes(); i++ )
            {
                LatticeNode node = ( LatticeNode ) children.get( i );

                //Remove the old copy of the Parent object from the ArrayList
                // as this will point to the original version
                //And not the copy that was created in this new ChildTree

                LatticeNode childNode = null;
                try
                {
                    childNode = childTree.searchTree( node.getName() );
                }
                catch ( NodeNotFoundException e )
                {
		    //True denotes that this is a copy
                    childNode = Config.getLatticeFactory().copyNode( node);
                }

                copyNode.addChild( childNode );

                //Add parents These should not be editable by the
                //Group accessing the data. Unless the Parent node is
                //also a child.
                LatticeNode childParent = null;
                ArrayList keyParents = keyNode.getParents();

                for ( int j = 0; j < keyParents.size(); j++ )
                {
                    String keyParentName = ( ( LatticeNode ) keyParents.get( j ) ).getName();
                    try
                    {
                        childParent = childTree.searchTree( keyParentName );
                    }
                    catch ( NodeNotFoundException e )
                    {
                        childParent = Config.getLatticeFactory().createNode( (LatticeNode)keyParents.get(j) );

                    }
                    copyNode.addParent( childParent );
                    childParent.addChild( copyNode );
                }

                childNode.addParent( copyNode );

                endNode = copyAllChildren( node, childNode, childTree );
            }

            return endNode;
        }
        catch ( InvalidLatticeStructureException e )
        {
            throw new InvalidLatticeStructureException(e.getMessage(), e.getDetail());
        }
    }

 /**
     *  Flatten the LatticeTree into an ArrayList to be used by the Generic
     *  Algorithms
     *
     *@param  keyNode                               Description of the Parameter
     *@param  copyNode                              Description of the Parameter
     *@param  childTree                             Description of the Parameter
     *@return                                       Description of the Return
     *      Value
     *@exception  InvalidLatticeStructureException  Description of the Exception
     */
    public Object copyOnlyChildren( LatticeNode keyNode, LatticeNode copyNode, LatticeTree childTree )
        throws InvalidLatticeStructureException
    {
        try
        {

            //Return the final Node that does not have any children.
            //This should be the Public Node
            //There will be one 'public' node for each branch in the ChildTree
            if ( keyNode.countChildNodes() == 0 )
            {
                if ( childTree.getRootNode() == null )
                {
                    childTree.setRootNode( copyNode );
                    return copyNode;
                }

                LatticeNode childRootNode = childTree.getRootNode();
                //Make sure that all branches are added to the root node
                //As copyNode will become the new Root Node
                for ( int i = 0; i < copyNode.getParents().size(); i++ )
                {
                    childRootNode.addParent( copyNode.getParents().get( i ) );
                }
                childTree.setRootNode( copyNode );
                return copyNode;
            }
            Object endNode = null;

            ArrayList children = keyNode.getChildren();

            //Get the CHildren and Add to the new LatticeTree
            for ( int i = 0; i < keyNode.countChildNodes(); i++ )
            {
                LatticeNode node = ( LatticeNode ) children.get( i );

                //Remove the old copy of the Parent object from the ArrayList
                // as this will point to the original version
                //And not the copy that was created in this new ChildTree

                LatticeNode childNode = null;
                try
                {
                    childNode = childTree.searchTree( node.getName() );
                }
                catch ( NodeNotFoundException e )
                {
		    //True denotes that this is a copy
                    childNode = Config.getLatticeFactory().copyNode( node);
                }

                copyNode.addChild( childNode );

                endNode = copyOnlyChildren( node, childNode, childTree );
            }

            return endNode;
        }
        catch ( InvalidLatticeStructureException e )
        {
            throw new InvalidLatticeStructureException(e.getMessage(), e.getDetail());
        }
    }


    /**
     *  Flatten the LatticeTree into an ArrayList to be used by the Generic
     *  Algorithms
     *
     *@param  flatLattice  Description of the Parameter
     *@param  rootNode     Description of the Parameter
     *@param  level        Description of the Parameter
     *@return              Description of the Return Value
     */
    public ArrayList<LatticeNode> flattenLattice( ArrayList<LatticeNode> flatLattice, LatticeNode rootNode, int level )
    {

        /*
         *  Flatten the LatticeTree into an ArrayList
         */
        if ( rootNode == null )
        {
            System.out.print( "In LF: flattenLattice(). Root node is null : " );
            return flatLattice;
        }
        ArrayList<LatticeNode> parents = rootNode.getParents();
        int geneIndex = flatLattice.size() - 1;
        level++;
        //Check to see if this current level is the max
        //Also check to see if this level has the max no of parents.
        checkMaxLevel( level );

        //Make sure that the sibling nodes are added before the Parents
        for ( int i = 0; i < rootNode.countParentNodes(); i++ )
        {
            //MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "LatticeFunctions.flattenLattice(). Root node " + rootNode.getName());

            if ( !flatLattice.contains( parents.get( i ) ) )
            {
                //MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "LatticeFunctions.flattenLattice(). Adding parent " + ((LatticeNode) parents.get(i)));
                //MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "LatticeFunctions.flattenLattice(). Adding parent " + ((LatticeNode) parents.get(i)).getName());

                flatLattice.add( parents.get( i ) );
                geneIndex = flatLattice.size() - 1;
                mapGeneToNode.put( new Integer( geneIndex ), parents.get( i ) );
                mapNodeToGene.put( parents.get( i ), new Integer( geneIndex ) );
            }

            //If a node has already been found it means that it was the
            //parent of another node. If this child was on a lower level than
            //the current child. The parent needs to be 'bumped up' to the
            //new higher level. Otherwise a parent could appear below a child in
            // the lattice.
            //This processing is automatically carried out in changeNodes perLevel
            changeNodesPerLevel( parents.get( i ), new Integer( level ) );
        }

        for ( int i = 0; i < rootNode.countParentNodes(); i++ )
        {
            flattenLattice( flatLattice, parents.get( i ), level );
        }

        if ( level == 1 )
        {
            NO_OF_NODES = flatLattice.size();
        }
        return flatLattice;
    }


    /**
     *  Description of the Method
     *
     *@param  root  Description of the Parameter
     *@return       Description of the Return Value
     */
    public int initialize( LatticeNode root )
    {
        //make sure that this is set so that INitialization will
        //only be carried out once per lattice


        if ( isSet )
        {
            return 0;
        }

        ArrayList<LatticeNode> flattenLattice = new ArrayList<LatticeNode>();
        flattenLattice = addRoot( flattenLattice, root );


        flattenLattice = flattenLattice( flattenLattice, root, 0 );

        //int maxLevelTest =
        calcMaxNodesPerLevel();

        //int noOfBoxes =
        getNoOfGrids();

        isSet = true;

        return flattenLattice.size();
    }


    /**
     *  Description of the Method
     *
     *@param  filename            Description of the Parameter
     *@return                     Description of the Return Value
     *@exception  MraldException  Description of the Exception
     */
    public LatticeTree recoverLatticeTree( String filename )
        throws MraldException, FileNotFoundException, IOException
    {
        try
        {
            log.info("Getting old lattice file" + filename);

            FileInputStream isStream = new FileInputStream( filename );
            ObjectInputStream q = new ObjectInputStream( isStream );
            return ( LatticeTree ) q.readObject();
        }
	catch ( FileNotFoundException e )
        {

            throw e;
        }
        catch ( ClassNotFoundException e )
        {
            throw new MraldException( "Class LatticeTree was not found in the classpth.  " +
                    "Please have your administrator check the installation.", e );
        }
        catch ( IOException e )
        {
            throw e;
        }

    }


    /**
     *  Description of the Method
     */
    public void reset()
    {
        mapGeneToNode = new HashMap<Integer,LatticeNode>();
        mapLevelCount = new HashMap<Integer,Integer>();
        mapNodeToGene = new HashMap<LatticeNode,Integer>();
        maplevelOfNode = new HashMap<LatticeNode,Integer>();
        maxLevel = 0;
        maxNodePerLevel = 0;

    }


    /**
     *  Serializes an LatticeTree to a file
     *
     *@param  filename         Path and name of the file to create/overwrite
     *@param  tree             The LatticeTree object to serialize
     *@exception  IOException  Description of the Exception
     */
    public void serializeLatticeTree( LatticeTree tree, String filename )
        throws IOException
    {
        FileOutputStream ostream = new FileOutputStream( filename );
        ObjectOutputStream p = new ObjectOutputStream( ostream );
        p.writeObject( tree );
        p.flush();
        p.close();
    }


    /**
     *@param  genesOnThisLevel  ArrayList of IntegerGene's representing the
     *      horizontal locations of the nodes
     *@return                   Description of the Return Value
     */
	@SuppressWarnings("unchecked")
    public ArrayList<IntegerGene> snap( ArrayList<IntegerGene> genesOnThisLevel )
    {
		// Assume that the elements of this list are comparable.
        java.util.Collections.sort( genesOnThisLevel );
        int minGridPos = 0;
        int gridPos = 0;
        int boxWidth = getBoxWidth();
        //System.out.println( "BoxWidth : " + boxWidth );

        for ( int i = 0; i < genesOnThisLevel.size(); i++ )
        {
            //System.out.println( "---------------- " );

            IntegerGene thisGene = genesOnThisLevel.get( i );
            //System.out.println( "Snap. initial x  : " + thisGene.intValue() );

            gridPos = ( thisGene.intValue() / boxWidth );
            if ( gridPos <= minGridPos )
            {
                gridPos = minGridPos + 1;
            }
            minGridPos = gridPos;
            //System.out.println( "Snap. Min Grid Pos  : " + minGridPos );
            //System.out.println( "Snap. Grid Pos  : " + gridPos );

            int newPos = ( gridPos * boxWidth ) - boxWidth / 2;
            thisGene.setAllele( new Integer( newPos ) );
            //System.out.println( "Snap. New x  : " + newPos );
            //System.out.println( "Snap. Allele  : " + thisGene.intValue() );
            //System.out.println( "---------------- " );
        }

        return genesOnThisLevel;
    }


    /**
     *  Takes the x value of the Node, and 'Snaps to Grid' to neaten
     *
     *@param  values  Description of the Parameter
     *@return         Description of the Return Value
     */
    public Gene[] snapToGrid( Gene[] values )
    {
        //Steps
        //1 : Get list of nodes on the same level
        //2 : Get associated Genes
        //3 : Get value assigned to each Gene, and find the minimum value
        //4 : Assign a value aligned with grid

        for ( int i = 0; i < mapLevelCount.size(); i++ )
        {
            ArrayList<LatticeNode> nodesOnThisLevel = getNodesOnLevel( i );
            ArrayList<IntegerGene> genesOnThisLevel = new ArrayList<IntegerGene>();
            HashMap<IntegerGene,LatticeNode> linkGenesToNodes = new HashMap<IntegerGene,LatticeNode>();

            for ( int j = 0; j < nodesOnThisLevel.size(); j++ )
            {
                LatticeNode newNode = nodesOnThisLevel.get( j );
                IntegerGene thisGene = ( IntegerGene ) values[getInteger( newNode ).intValue()];
                genesOnThisLevel.add( thisGene );

                linkGenesToNodes.put( thisGene, newNode );
            }
            snap( genesOnThisLevel );
        }

        return values;
    }


    /**
     *  Description of the Method
     *
     *@param  genesOnThisLevel  Description of the Parameter
     *@return                   Description of the Return Value
     */
    @SuppressWarnings("unchecked")
    public ArrayList<IntegerGene> spaceNodes( ArrayList<IntegerGene> genesOnThisLevel )
    {
        /*
         *  space out nodes that are too close to one another.  Repeat until we are OK, or we try it 10 times
         */
    	// Assume that the elements of this list are comparable.
        java.util.Collections.sort( genesOnThisLevel );
        int MIN_SPACING = getBoxWidth() * 2/3;

	if (MIN_SPACING < 100) MIN_SPACING = 100;
        IntegerGene thisGene;
        IntegerGene previousGene;
        boolean ok = false;
        int iterations = 0;
        while ( !ok && iterations < 10 )
        {
            ok = true;
            StringBuffer ret = new StringBuffer();
            previousGene = genesOnThisLevel.get( 0 );
            ret.append( previousGene.intValue() );
            for ( int i = 1; i < genesOnThisLevel.size(); i++ )
            {
                thisGene = genesOnThisLevel.get( i );
                ret.append( thisGene.intValue() );
                if ( thisGene.intValue() - previousGene.intValue() < MIN_SPACING )
                {
                    ok = false;
                    int spacingDeficiet = MIN_SPACING - ( thisGene.intValue() - previousGene.intValue() );
                    thisGene.setAllele( new Integer( thisGene.intValue() + spacingDeficiet / 2 ) );
                    previousGene.setAllele( new Integer( previousGene.intValue() - spacingDeficiet / 2 ) );
                }
                previousGene = thisGene;
            }

            iterations++;
        }
        /*
         *  now move the whole level to the center of the pic
         *  find the mid point of the row and move it to the mid point for the screen
         */
        IntegerGene leftGene = genesOnThisLevel.get( 0 );
        IntegerGene rightGene = genesOnThisLevel.get( genesOnThisLevel.size() - 1 );
        int rowMidPoint = ( leftGene.intValue() + rightGene.intValue() ) / 2;
        int screenMidPoint = screenWidth / 2;
        int move = screenMidPoint - rowMidPoint;
        for ( int i = 0; i < genesOnThisLevel.size(); i++ )
        {
            thisGene = genesOnThisLevel.get( i );
            thisGene.setAllele( new Integer( thisGene.intValue() + move ) );
        }
        return genesOnThisLevel;
    }


    /**
     *  Description of the Method
     *
     *@param  values  Description of the Parameter
     *@return         Description of the Return Value
     */
    public Gene[] spaceOutLevels( Gene[] values )
    {
        //Steps
        //1 : Get list of nodes on the same level
        //2 : Get associated Genes
        //3 : Get value assigned to each Gene, and find the minimum value
        //4 : Space out all the levels

        for ( int i = 0; i < mapLevelCount.size(); i++ )
        {
            ArrayList<LatticeNode> nodesOnThisLevel = getNodesOnLevel( i );
            ArrayList<IntegerGene> genesOnThisLevel = new ArrayList<IntegerGene>();
            //HashMap linkGenesToNodes = new HashMap();

            for ( int j = 0; j < nodesOnThisLevel.size(); j++ )
            {
                LatticeNode newNode = nodesOnThisLevel.get( j );
                IntegerGene thisGene = ( IntegerGene ) values[getInteger( newNode ).intValue()];
                genesOnThisLevel.add( thisGene );
                //linkGenesToNodes.put( thisGene, newNode );
            }
            spaceNodes( genesOnThisLevel );
        }
        return values;
    }

    /**
     *  Gets the body attribute of the BuildForm object
     *
     *@return                   The body value
     *@exception  SQLException  Description of the Exception	 try{
            */
    @SuppressWarnings("unchecked")
    public java.util.List sort(ArrayList groupList)
    {

    	// Assume the elements of the list are comparable.
       java.util.Collections.sort(groupList);
       return groupList;
    }

    /***Wrap up the Nodes in the new Nodes before serializing
    */
    public LatticeTree wrapTree(LatticeTree tre) throws InvalidLatticeStructureException
    {
	   return new LatticeTree( wrapNodes( Config.getLatticeFactory().createNode("Public"), tre.getRootNode() ) );
    }

    /***Wrap up the Nodes in the new Nodes before serializing
    */
    public LatticeNode wrapNodes(LatticeNode wrappedChildNode, LatticeNode node) throws InvalidLatticeStructureException
    {
	//Wrap the node with no Children. These will be added retrospectively
        LatticeNode newNode = Config.getLatticeFactory().createNodeWithoutChildren( node);

	//Make sure that the sibling nodes are added before the Parents
        for ( int i = 0; i < node.countParentNodes(); i++ )
        {

	    ArrayList parents = node.getParents();

            //MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "LatticeFunctions.flattenLattice(). Root node " + rootNode.getName());
            LatticeNode wrappedNode = wrapNodes( newNode, ( LatticeNode ) parents.get( i) );

	    wrappedNode.addChild( wrappedChildNode);
        }
	return newNode;
    }

     /**
     *  Gets the body attribute of the BuildForm object
     *
     *@return                   The body value
     *@exception  SQLException  Description of the Exception	 try{
            */
    public int getScreenWidth()
    {
       return screenWidth;
    }

    /**
	 *@param  rootNode                              The feature to be added to the
	 *      LabNodes attribute
	 *@param  labNodes                              The feature to be added to the
	 *      LabNodes attribute
	 *@return                                       Description of the Return Value
	 *@exception  NullLatticeNodeException          Description of the Exception
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	private ArrayList addLabNodes(LatticeNode rootNode, ArrayList labNodes)
		 throws NullLatticeNodeException, InvalidLatticeStructureException
	{

		//ArrayList parents = rootNode.getParents();

		/*for (int i = 0; i < parents.size(); i++)
		{
			LatticeNode currNode = (LatticeNode) parents.get(i);
			//if (currNode.getType().equals(NeuroConstants.LAB_TYPE))
			//{
				if (!labNodes.contains(currNode))
				{
					labNodes.add(currNode);
				}
			//}

			labNodes = addLabNodes(currNode, labNodes);

		}*/
		return labNodes;
	}
}

